#! /usr/bin/env Python3
#coding=utf-8
"""
常用类型转换用函数
作者: KydGroups
版本: 1.0.0
完成日期: 2024-02-22
修改记录:
"""
from datetime import datetime

def __CheckAndSplitStrNumber(strInput:str=''):
    """
    :DESCRIPTION:
        检查输入的字符串是否由正确的数字组成,如果不是则返回正确分隔后的字符串列表
    :PARAMS:
        strInput: 输入的字符串
    :RETURNS:
        None:没有错误 List:有错误发生,数字字符列表,只有一个数字也是列表返回
    """
    if not strInput: #如果没有传入参数
        return [""] #返回空值
    listReturn = [] #返回错误值用列表
    if strInput[-1] in ['.', '-']: #如果最后一个字符无效,则去掉
        listReturn.append(strInput[:-1])
        return listReturn

    if strInput[0] == '.': #如果第一个字符无效
        listReturn.append(strInput[1:])
        return listReturn

    nPos = strInput.find('-', 1) #检查有没有两个负号,如果有则分隔
    if nPos != -1: #如果还有
        listReturn.append(strInput[:nPos])
        listReturn.append(strInput[nPos:])
        return listReturn

    nPos = strInput.find('.') #检查有没有两个点号或点号前为负号的情况,如果有则分隔
    if nPos != -1: #如果有
        if strInput[(nPos-1)] == '-': #如果点号前为负号
            listReturn.append(strInput[:(nPos-1)]) #去掉负号
            listReturn.append(strInput[(nPos+1):]) #去掉点号
            return listReturn
        else: #如果没有负号,则检查重复的点号
            nPos = strInput.find('.', (nPos+1)) #从下一个字符开始检查
            if nPos != -1: #如果还有
                listReturn.append(strInput[:nPos])
                listReturn.append(strInput[(nPos+1):]) #去掉点号
                return listReturn
    if len(strInput) >= 2: #如果超过2个字符,则检查首个数字为0的问题
        if strInput[0] == '0': #如果首字符为0
            if strInput[1] != '.': #如果0后面不跟点号
                listReturn.append(strInput[1:])
                return listReturn
    return None


def StrToColorStr(strConvert:str='', strType:str=''):
    """
    :DESCRIPTION:
        将字符串转换为颜色显示的字符串
        这个函数在windows中putty中显示正常,在deepin的终端中显示主题deepin不正常,主题cai、argonaut、aci等正常
    :PARAMS:
        strConvert: 要显示的字符串
        strType: 显示的字符串类型
    :RETURNS:
        None:没有数据可以转换 Str:转换后的颜色字符串
    """
    dictType = dict()#显示模式
    dictType["alarm"] = {'Top':'\033[5;31m', 'Tail':'\033[0m'} #警告色使用红色
    dictType["red"] = {'Top':'\033[1;31m', 'Tail':'\033[0m'}
    dictType["blue"] = {'Top':'\033[1;34m', 'Tail':'\033[0m'}
    dictType["yellow"] = {'Top':'\033[1;33m', 'Tail':'\033[0m'} #
    dictType["green"] = {'Top':'\033[1;32m', 'Tail':'\033[0m'} #
    dictType["white"] = {'Top':'\033[1;37m', 'Tail':'\033[0m'} #

    if not strConvert: #如果没有要显示的字符串，则返回None
        return None
    if not strType: #如果没有显示模式，则直接返回输入字符串
        return strConvert
    if not isinstance(strConvert, str): #如果传入的不是字符串
        return None
    if strType in dictType.keys():#如果设置的是可用的颜色模式
        return dictType[strType]['Top'] + strConvert + dictType[strType]['Tail']
    else: #不可用则直接返回输入的字符串
        return strConvert


def StrTopNumToFloat(strInput:str='', fDefault=None):
    """
    :DESCRIPTION:
        数值转换,字符串前面的数字部分转换为浮点数,不是字符串的部分丢弃,第一个字符不是数据字符也丢弃
    :PARAMS:
        strInput: 字符串,必须是字符串类型,别的类型不可以
        fDefault: 默认值,如果有则返回默认值
    :RETURNS:
        None:没有传入参数或参数错误 Str:字符串头部的数据
    """
    listFloat = ['0','1','2','3','4','5','6','7','8','9','-','.','e','E'] #浮点可用字符
    listSingle = ['.','e','E'] #只能出现一次的字符,注意检查的顺序
    if not strInput: #如果没有传入参数
        return fDefault
    if not isinstance(strInput, str): #如果传入的不是字符串
        return fDefault
    strTemp = strInput.strip() #去掉字符串前后空格
    strNum = "" #保存数字用
    try:
        for num in range(len(strTemp)): #循环保留前部可以字符
            if strTemp[num] in listFloat:
                strNum += strTemp[num]
            elif strTemp[num] == ',': #如果是,号,表示千分位跳过
                continue
            else: #其它字符中断循环
                break

        for num in range(len(listSingle)): #处理只能出现一次的字符
            nPos = strNum.find(listSingle[num])
            if nPos == -1: #如果没有出现,则跳过
                continue
            else: #如果了一次
                nPos = strNum.find(listSingle[num], (nPos+1)) #从后一个字符开始查找
                if nPos == -1: #如果没有出现,则表示正常
                    continue
                else:
                    strNum = strNum[:nPos] #截断后面的内容

        nPos = strNum.find('-', 1) #处理负号问题,查找是否出现第二个负号
        if nPos != -1: #如果从第二个字符后出现了负号
            if strNum[(nPos-1)].upper() == 'E': #如果负号前一个字符是浮点标志,则再检查一下后面是否还有
                nPos = strNum.find('-', (nPos+1))
                if nPos != -1: #如果又出现一次
                    strNum = strNum[:nPos] #截断后面的内容
            else: #如果负号前一个字符不是浮点标志
                strNum = strNum[:nPos] #截断后面的内容

        if strNum[-1].upper() in listSingle: #检查最后一个字符是否为特殊字符
            return float(strNum[:-1])
        else:
            return float(strNum)
    except:
        return fDefault


def StrSplitNumerToList(strInput:str='', nSize:int=0):
    """
    :DESCRIPTION:
        将字符串中的数字生成列表
        注意:
            逗号分隔的数字会被截断,所有千分位计数不可用
            科学计数法的浮点数不可使用
    :PARAMS:
        strInput: 输入的字符串
        nSize: 截取字符串长度,默认为0表示全部字符串
    :RETURNS:
        None:没有输入或没有数字 List:数字字符列表,只有一个数字也是列表返回
    """
    listFloat = ['0','1','2','3','4','5','6','7','8','9','.', '-'] #浮点可用字符
    if not strInput: #如果没有传入参数
        return None
    nMax = len(strInput) #获取字符串长度
    nLen = nMax
    if 0 < nSize < nMax: #如果传入正确的长度参数
        nLen = nSize
    try:
        listNum = [] #数字列表
        strNum = "" #临时用
        for num in range(nLen):
            if strInput[num] in listFloat: #如果字符可用,则添加
                strNum += strInput[num]
            else: #字符不可用,则截断
                if len(strNum) >= 1: #只要有数据
                    listNum.append(strNum)
                strNum = ""
        else:
            if len(strNum) >= 1:
                listNum.append(strNum)
        bError = True
        while bError: #循环检查错误,直到全部检查完毕
            bError = False #重置错误标志
            listTemp = [] #临时使用列表
            for item in listNum: #循环检查第一个元素
                if len(item) <= 0: #如果为空字符串
                    continue
                else:
                    listCheck = __CheckAndSplitStrNumber(item)
                    if not listCheck:#如果没有返回,则表示没有错误
                        listTemp.append(item)
                    else: #有返回,则表示有错误
                        listTemp += listCheck #检查没有错误则将这一个数据添加
                        bError = True #设置错误标志
            listNum = listTemp
        return listNum
    except:
        return None


def DateLongToSqlStr(lDate:int=0):
    """
    :DESCRIPTION:
        日期转换,将long日期格式YYYYMMDD转换为Sql日期格式YYYY-MM-DD
    :PARAMS:
        lDate: long日期,如20000101
    :RETURNS:
        None:错误日期 Str:YY-MM-DD格式的字符串
    """
    if not lDate: #如果没有传入参数
        return None
    iTempDate = 0 #用于存储日期
    bPre = False #标记是否为公元前
    try:
        if isinstance(lDate, str):#如果传入的是字符串格式
            if lDate.isdigit():#如果传入的是数字
                iTempDate = int(lDate)
            else:
                return None
        else:
            iTempDate = lDate
        if iTempDate <= 0: #如果是公元前的日期
            bPre = True
            iTempDate = abs(iTempDate) #取绝对值
        iYear = int( iTempDate / 10000)
        iMonth = int( (iTempDate % 10000) / 100)
        iDay = iTempDate % 100
        if bPre: #如果是公元前日期,则添加-号
         strReturn = "-%04d-%02d-%02d" % (iYear, iMonth, iDay)
        else: #公元后的日期
            strReturn = "%04d-%02d-%02d" % (iYear, iMonth, iDay)
        return strReturn
    except:
        return 0


def DateSqlStrToLong(strDate:str=''):
    """
    :DESCRIPTION:
        日期转换,将Sql日期格式YYYY-MM-DD转换为long日期格式YYYYMMDD
    :PARAMS:
        strDate: mysql日期格式
    :RETURNS:
        0:无参数传入,或数据错误, int:日期格式
    """
    iDate = 0 #转换后的long日期
    bPre = False #标记是否为公元前
    if not strDate:#如果没有传入参数
        return 0
    if not isinstance(strDate, str): #如果传入的不是字符串
        return 0
    try:
        if strDate[0] == '-': #如果第一个字符是-,则表示公元胶
            bPre = True
        strTemp = strDate.replace("-","") #字符串去掉-
        strTemp.strip() #去掉空格
        if strTemp.isdigit():#如果字符串只由数字构成
            if bPre:
                iDate = int(strTemp) * -1
            else:
                iDate = int(strTemp)
        else:
            iDate = 0
        return iDate
    except:
        return 0


def DateCnToSqlStr(strCn:str=''):
    """
    :DESCRIPTION:
        日期转换,将中文日期YYYY年MM月DD日转换为Sql日期格式YYYY-MM-DD
    :PARAMS:
        strCn: 中文日期 YYYY年MM月DD日
    :RETURNS:
        None:没有输入参数,或数据错误 Str:YYYY-MM-DD格式的字符串
    """
    strDate = "" #转换后的日期
    bPre = False #标记公元前
    if not strCn: #如果没有输入参数
        return None
    if not isinstance(strCn, str): #如果传入的不是字符串
        return None
    try:
        strDate = strCn.strip()
        if strCn.find('公元前') != -1: #如果是公元前
            strDate = strDate[3:] #去掉公元前标志
            bPre = True 
        strDate = strDate[:-1] #去掉日
        strDate = strDate.replace("年", "-")
        strDate = strDate.replace("月", "-")
        if bPre: #如果是公元前
            return ("-%s" % strDate)
        else:
            return strDate
    except:
        return None


def DateSqlStrToCn(strDate:str=''):
    """
    :DESCRIPTION:
        日期转换,将Sql日期格式YYYY-MM-DD转换为中文日期YYYY年MM月DD日
    :PARAMS:
        strCn: Sql字符串日期,YYYY-MM-DD
    :RETURNS:
        None:没有输入参数,或数据错误 Str:YYYY-MM-DD格式的字符串
    """
    strCn = "" #转换后的日期
    bPre = False #标记公元前
    if not strDate: #如果没有传入参数
        return None
    if not isinstance(strDate, str): #如果传入的不是字符串
        return None
    try:
        if strDate[0] == '-': #如果传入的是公元前日期
            bPre = True
            strCn = strDate[1:] #去掉公元前标志
        else: #如果不是公元前,则不需要处理
            strCn = strDate
        if bPre: #如果是公元前
            return ("公元前%s年%s月%s日" % (strCn[:-6], strCn[-5:-3], strCn[-2:]) )
        else:
            return ("%s年%s月%s日" % (strCn[:-6], strCn[-5:-3], strCn[-2:]) )
    except:
        return None


def DateClsToSqlStr(clsDate=None):
    """
    :DESCRIPTION:
        日期转换,将python日期类型转换为Sql日期格式YYYY-MM-DD
    :PARAMS:
        clsDate: python日期类
    :RETURNS:
        None:没有传入参数,或参数错误 Sql:YYYY-MM-DD日期格式
    """
    if not clsDate: #如果没有传入参数
        return None
    try:
        strDate = clsDate.strftime("%Y-%m-%d")
        return strDate
    except:
        return None


def DateSqlStrToCls(strDate=''):
    """
    :DESCRIPTION:
        日期转换,Sql日期格式YYYY-MM-DD转换为python datetime格式
    :PARAMS:
        strDate: Sql日期格式YYYY-MM-DD
    :RETURNS:
        None:没有传入参数,或参数错误 datetime:转换成功
    """
    if not strDate:
        return None
    if not isinstance(strDate, str):
        return None
    try:
        clsDate = datetime.strptime(strDate, '%Y-%m-%d')
        return clsDate
    except:
        return None


def KeyValueToEqualStr(value, strKey:str='', nFloat:int=4, noneValue:str='0', bStrMark:bool=False):
    """
    :DESCRIPTION:
        将键值对转换为strKey=value形式的字符串
    :PARAMS:
        value: 值
        strKey: 键名称,必须是字符串
        nFloat: 浮点数保留小数位数
        noneValue: 当值为None时处理方式,必须为字符串形式
        bStrMark: 如果值是字符串形式,是否使用单引号包括
    :RETURNS:
        None:出错  Str:转换成功
    """
    if not strKey:
        return None
    try:
        strReturn = ""
        if value is None: #如果值为None
            strReturn = "%s='%s'" % (strKey, noneValue)
        elif isinstance(value, int): #如果值为int
            strReturn = "%s=%d" % (strKey, value)
        elif isinstance(value, float): #如果值为float
            strFormat = "%%s=%%.%df" % nFloat #格式化字符串,确定浮点数有效数字
            strReturn = strFormat % (strKey, value)
        elif isinstance(value, str): #如果值为str
            if bStrMark: #如果字符串需要单引号包括
                strReturn = "%s='%s'" % (strKey, value)
            else:
                strReturn = "%s=%s" % (strKey, value)
        else: #其他情况,表示出错误,不允许其它值
            return None
        return strReturn
    except:
        return None


def Dict2TupleList(dictData, listKey:list=[], bNotStrKey=True):
    """
    :DESCRIPTION:
        将字典转换为list[tuple(key, value)...]无组列表形式
    :PARAMS:
        dictData: 需要转换的字典数据,没有参数则返回None
        listKey: 指定的需要转换的字典的键值,如果没有参数,则使用字典的全部Key;如果传入参数错误返回None
        bNotStrKey: 如果为True,则当Key不为字符串形式时返回None
    :RETURNS:
        None:错误 List:转换后的Tuple列表
    """
    if not dictData:
        return None
    try:
        listFields = [] #需要转换的Key值列表
        if not listKey: #如果没有传入指定的Key值列表
            listFields = list(dictData.keys()) #使用字典的全部键值
        else:
            listFields = listKey #使用指定的键值
            for key in listFields: #判断指定的键值是否正确
                if key not in dictData.keys(): #如果指定的键值不在字典的键中
                    return None #出错退出
        if bNotStrKey: #如果设置了,则检查Key是否都为Str形式
            for key in listFields:
                if not isinstance(key, str):
                    return None
        listTuple = []
        for key in listFields: #循环处理每一个键值数据
            listTuple.append((key, dictData[key]))
        return listTuple
    except:
        return None


def Dict2TupleStr(dictData, listKey:list=[], nFloat:int=4, noneValue:str='0'):
    """
    :DESCRIPTION:
        将字典转换为Str格式的tuple"(key=value,...)",主要用于转换数据库语句
        int、float、bool、None都将进行转换
    :PARAMS:
        dictData: 需要转换的字典数据,没有参数则返回None
        listKey: 指定的需要转换的字典的键值,必须全部的字符串格式,如果没有参数,则使用字典的全部Key;如果传入参数错误返回None
        nFloat: 浮点数保留多少位小数
        noneValue: 当键值为None时的处理方式,该值必须为字符串形式
    :RETURNS:
        None:没有错误,或参数错误 Str:转换为后字符串
    """
    listTuple = Dict2TupleList(dictData=dictData, listKey=listKey, bNotStrKey=True) #将字典转换为tuple列表形式
    if not listTuple: #如果转换失败
        return None
    try:
        strReturn = "("
        for item in listTuple: #循环处理每一条数据
            strTemp = KeyValueToEqualStr(value=item[1], strKey=item[0], nFloat=nFloat, noneValue=noneValue, bStrMark=True)
            if not strTemp: #如果转换字符串错误
                return None
            else:
                strTemp = strTemp + "," #添加逗号分隔
            strReturn += strTemp
        strReturn = strReturn[:-1] + ")" #将最后一个点号转换为)符号
        return strReturn
    except:
        return None


def Dict2InsertSqlStr(dictData, tableName:str='', listKey:list=[], bReplace=False, nFloat:int=4, noneValue:str='0'):
    """
    :DESCRIPTION:
        根据字典内容生成相应的Sql插入指令
    :PARAMS:
        dictData: 字典数据
        tableName: 数据表名
        listKey: 段名列表,可选:如果没有设置，则使用字典全部键作为段名
        bReplace: 是否为Sql替换指令
        nFloat: 浮点数保留多少位小数
        noneValue: 当键值为None时的处理方式,该值必须为字符串形式
    :RETURNS:
        None:错误 Str:生成的SQL指令字符串
    """
    if not dictData:
        return None
    if not tableName:
        return None
    try:
        listFields = [] #操作用段名列表
        if not listKey: #如果传入了指定的段名列表
            listFields = list(dictData.keys())
        else: #没有指定则使用字典的全部键名作为段名
            listFields = listKey
        strValue = Dict2TupleStr(dictData=dictData, listKey=listFields, nFloat=nFloat, noneValue=noneValue) #转换插入值字符串
        if not strValue: #如果转换插入值错误
            return None
        if bReplace:  # 创建SQL指令头部，如果是替换
            strBegin = "REPLACE INTO %s" % tableName
        else:
            strBegin = "INSERT INTO %s" % tableName
        strTitle = "(" + ",".join(listFields) + ") "  # 生成要插入的字段名
        strValue = "VALUES" + strValue
        strSql = strBegin.strip() + strTitle.strip() + " " +strValue.strip()
        return strSql
    except:
        return None


def Dict2MultiInsertSqlStr(listData:list=[], tableName:str='', listKey:list=[], bReplace=False, nFloat:int=4, noneValue:str='0'):
    """
    :DESCRIPTION:
        根据字典内容生成相应的Sql插入指令
    :PARAMS:
        listData: 字典列表数据
        tableName: 数据表名
        listKey: 段名列表,可选:如果没有设置，则使用字典全部键作为段名
        bReplace: 是否为Sql替换指令
        nFloat: 浮点数保留多少位小数
        noneValue: 当键值为None时的处理方式,该值必须为字符串形式
    :RETURNS:
        None:错误 Str:生成的SQL指令字符串
    """
    if not listData:
        return None
    if not tableName:
        return None
    try:
        listFields = [] #操作用段名列表
        if not listKey: #如果传入了指定的段名列表
            listFields = list(listData[0].keys()) #使用字典列表中第一个元素的keys
        else: #没有指定则使用字典的全部键名作为段名
            listFields = listKey
        strBegin = "" #指令头部
        if bReplace:  # 创建SQL指令头部，如果是替换
            strBegin = "REPLACE INTO %s" % tableName
        else:
            strBegin = "INSERT INTO %s" % tableName
        strTitle = "(" + ",".join(listFields) + ") "  # 生成要插入的字段名
        strValue = "VALUES"
        for item in listData:  # 循环处理每个字典
            strTuple = Dict2TupleStr(dictData=item, listKey=listFields, nFloat=nFloat, noneValue=noneValue)
            if not strTuple: #只要出现一次错误
                print(StrToColorStr("Data Error", 'red')) #显示警告语句
                print(item)
                return None
            else: #转换成功
                strValue += strTuple + "," #添加每一组值之间的逗号
        strSql = strBegin + strTitle + " " + strValue[:-1]   #转换生成sql指令字符串
        return strSql
    except:
        return None


def Dict2UpdateSqlStr(dictData, tableName:str='', listKey:list=[], primaryKey:str='', nFloat:int=4, noneValue:str='0'):
    """
    :DESCRIPTION:
        根据字典内容生成相应的Sql插入指令
    :PARAMS:
        dictData: 字典数据
        tableName: 数据表名
        listKey: 段名列表,可选:如果没有设置，则使用字典全部键作为段名
        primaryKey: 更新主键,必须在字典键列表中,如果没有输入则使用listKey的第一个主键,否则使用dictData.keys()的第一个主键
        nFloat: 浮点数保留多少位小数
        noneValue: 当键值为None时的处理方式,该值必须为字符串形式
    :RETURNS:
        None:错误 Str:生成的SQL指令字符串
    """
    if not dictData:
        return None
    if not tableName:
        return None
    try:
        listFields = [] #操作用段名列表
        if not listKey: #如果传入了指定的段名列表
            listFields = list(dictData.keys()) #使用字典的keys
        else: #没有指定则使用字典的全部键名作为段名
            listFields = listKey
        if not primaryKey:  # 如果没有设置索引关键字，则使用keyList的第一个关键字
            primaryKey = listFields[0] #使用关键值的第一个元素作为主键
            listFields = listFields[1:]  # 将关键字列表减去索引关键字
        else: #如果设置了主键,则检查是否在字典的键列表中
            if primaryKey not in dictData.keys():
                return None
        strBegin = "UPDATE %s SET" % tableName  #创建SQL指令头部
        strValue = Dict2TupleStr(dictData=dictData, listKey=listFields, nFloat=nFloat, noneValue=noneValue) #转换tupleStr
        if not strValue: #如果转换失败
            return None
        else: #如果转换成功
            strValue = strValue[1:-1] #去掉前后的圆括号
        strPri = "WHERE "  #索引关键值
        tempStr = KeyValueToEqualStr(value=dictData[primaryKey], strKey=primaryKey, nFloat=nFloat, bStrMark=True) #转换主键对应的值对
        if not tempStr: #如果转换主键值错误
            return None
        else:
            strPri = strPri + tempStr
        strSql = strBegin + " " + strValue + " " + strPri #生成SQL更新指令
        return strSql
    except:
        return None
